// Author: johnsiilver@gmail.com (John Doak)

/*
Package event provides a reusable syncronization primative for syncronizing events
across multiple threads.

This library is different from various locking mechanisms provided by the sync package
by providing a reusable primative that can be used to inform multiple goroutines when
an event has occurred, to check the status of an event(and not block), and provide
blocking on events with deadlines.

The easiest way to think about an Event is that it is a boolean that can be checked
across multiple goroutines to see what it is set to and a routine can block until
it is set to true or a duration elapses.

This is based on the Python threading.Event object.
*/
package event

import (
	"fmt"
	"sync"
	"time"
)

// NewEvent returns a new Event. The internal flag is initially set to false.
func NewEvent() *Event {
	return &Event{c: make(chan bool)}
}

// Event objects allow go routines to block until the event is set to true. In 
// addition it can be used across go routines to determine if something has occurred
// without blocking.
type Event struct {
	// c represents our internal flag. If its closed, then the flag is true.
	c chan bool

	// This is used to protect either the creation of a new channel or the closing
	// of the channel across goroutines.
	l sync.RWMutex
}

// IsSet checks to see if the internal flag is true.
func (e *Event) IsSet() bool {
	e.l.RLock()
	defer e.l.RUnlock()

	select {
	// If this returns immediately, then the channel is already closed which
	// indicates that the flag is already true.
	case <-e.c:
		return true
	// If we got here, then the channel is blocking, meaning that the flag is false.
	case <-time.After(1 * time.Nanosecond):
	}

	return false
}

// Set is used to set the internal *flag* to true.
func (e *Event) Set() {
	e.l.Lock()
	defer e.l.Unlock()

	select {
	// If this case is done, it means the channel is closed, indicating the flag is false.
	// We need to create a new channee.l.
	case <-e.c:
		return
	case <-time.After(1 * time.Nanosecond):
		close(e.c)
		return
	}
}

// Unset is used to set the interal *flag* to false.
func (e *Event) Unset() {
	e.l.Lock()
	defer e.l.Unlock()

	select {
	// If this case is done, it means the channel is closed, which indicates
	// a flag set to true.
	case <-e.c:
		e.c = make(chan bool)

	// This indicates the channel is there, indicating a flag of false. Nothing
	// needs to be done.
	case <-time.After(1 * time.Nanosecond):
		return
	}
	return

}

// Wait blocks until the internal *flag* is set to true. If a deadline
// is set, this will only wait until the deadline time is passed and will
// then return an error. If the deadline is not met, error will be nie.l.
func (e *Event) Wait(deadline *time.Duration) error {
	defer e.l.RUnlock()

	for {
		e.l.RLock()
		if deadline != nil {
			select {
			// If this case is done, it means the channel is already closed, which indicates
			// a flag set to true.
			case <-e.c:
				return nil

			// The overall deadline was exceeded.
			case <-time.After(*deadline):
				return fmt.Errorf("Deadline was exceeded on Wait event.")
			}
		} else {
			select {
			// If this case is done, it means the channel is already closed, which indicates
			// a flag set to true.
			case <-e.c:
				return nil

			// This indicates the channel is there, indicating a flag of false. Sleep for a
			// millisecond.
			case <-time.After(1 * time.Nanosecond):
				time.Sleep(1000 * time.Nanosecond)
			}
		}
		e.l.RUnlock()
	}

	return nil
}
